經過上篇苦戰之後,終於進到最後講解JTAG訊號控制的部分!
本篇可能會有點長和無聊!
筆者盡量以程式碼配合範例的方式說明!
  
  
  
在開始介紹JTAG Operation之前,我們先來複習一下「Day 24: 您不可不知的FT2232H (2/3) - MPSSE Command Processor」中,"2. JTAG Control"的其中幾個會用到的Command,主要是控制TDI、TDO和TMS,
並看看實際程式碼!!
  
  
以目前實作來說,主要是使用以下兩種Command,用來做TDI傳輸的部分
前者主要在TDI資料長度大於1個Byte的時候,以Bytes的方式送出;
後者主要用在資料長度小於1個Byte的時候,以Bits的方式送出
來看看實際程式實作的部分,請參考(src/jtag/drivers/mpsse.c):
void mpsse_clock_data(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
    unsigned in_offset, unsigned length, uint8_t mode)
{
    /* TODO: Fix MSB first modes */
    DEBUG_IO("%s%s %d bits", in ? "in" : "", out ? "out" : "", length);
    if (ctx->retval != ERROR_OK) {
        DEBUG_IO("Ignoring command due to previous error");
        return;
    }
    /* TODO: On H chips, use command 0x8E/0x8F if in and out are both 0 */
    if (out || (!out && !in))
        mode |= 0x10;
    if (in)
        mode |= 0x20;
    while (length > 0) {
        /* Guarantee buffer space enough for a minimum size transfer */
        if (buffer_write_space(ctx) + (length < 8) < (out || (!out && !in) ? 4 : 3)
                || (in && buffer_read_space(ctx) < 1))
            ctx->retval = mpsse_flush(ctx);
        if (length < 8) {
            /* Transfer remaining bits in bit mode */
            buffer_write_byte(ctx, 0x02 | mode);
            buffer_write_byte(ctx, length - 1);
            if (out)
                out_offset += buffer_write(ctx, out, out_offset, length);
            if (in)
                in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
            if (!out && !in)
                buffer_write_byte(ctx, 0x00);
            length = 0;
        } else {
            /* Byte transfer */
            unsigned this_bytes = length / 8;
            /* MPSSE command limit */
            if (this_bytes > 65536)
                this_bytes = 65536;
            /* Buffer space limit. We already made sure there's space for the minimum
             * transfer. */
            if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
                this_bytes = buffer_write_space(ctx) - 3;
            if (in && this_bytes > buffer_read_space(ctx))
                this_bytes = buffer_read_space(ctx);
            if (this_bytes > 0) {
                buffer_write_byte(ctx, mode);
                buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
                buffer_write_byte(ctx, (this_bytes - 1) >> 8);
                if (out)
                    out_offset += buffer_write(ctx,
                            out,
                            out_offset,
                            this_bytes * 8);
                if (in)
                    in_offset += buffer_add_read(ctx,
                            in,
                            in_offset,
                            this_bytes * 8,
                            0);
                if (!out && !in)
                    for (unsigned n = 0; n < this_bytes; n++)
                        buffer_write_byte(ctx, 0x00);
                length -= this_bytes * 8;
            }
        }
    }
}
好,結束XDD 太長了.....!
一樣,我們拆成N個部份來解釋!
這邊先提醒一下,參數中的mode如果沒有特別說明,一律用以下JTAG_MODE的設定!
// src/jtag/drivers/mpsse.h
#define POS_EDGE_OUT 0x00
#define POS_EDGE_IN 0x00
#define LSB_FIRST 0x08
// src/jtag/drivers/ftdi.c
#define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT)
首先是判斷欲執行的動作是否包含寫入TDI,或是讀取TDO:
    if (out || (!out && !in))
        mode |= 0x10;
    if (in)
        mode |= 0x20;
並將對應的Bit 4/5拉起來,還記的以下編碼的規則嗎XD!?
再來是迴圈處理的部分,簡單的來說就是把Data拆成Bytes和Bits兩部分來處理!
首先是Bits傳輸的部分:
            /* Transfer remaining bits in bit mode */
            buffer_write_byte(ctx, 0x02 | mode);
            buffer_write_byte(ctx, length - 1);
            if (out)
                out_offset += buffer_write(ctx, out, out_offset, length);
            if (in)
                in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length);
            if (!out && !in)
                buffer_write_byte(ctx, 0x00);
            length = 0;
這邊很簡單,基本上就是用
再來是Bytes傳輸的部分,首先是檢查一下資料的長度不能大於65536個Bytes(傳輸上限):
           /* Byte transfer */
            unsigned this_bytes = length / 8;
            /* MPSSE command limit */
            if (this_bytes > 65536)
                this_bytes = 65536;
            /* Buffer space limit. We already made sure there's space for the minimum
             * transfer. */
            if ((out || (!out && !in)) && this_bytes + 3 > buffer_write_space(ctx))
                this_bytes = buffer_write_space(ctx) - 3;
            if (in && this_bytes > buffer_read_space(ctx))
                this_bytes = buffer_read_space(ctx);
接著是傳輸Bytes:
            if (this_bytes > 0) {
                buffer_write_byte(ctx, mode);
                buffer_write_byte(ctx, (this_bytes - 1) & 0xff);
                buffer_write_byte(ctx, (this_bytes - 1) >> 8);
                if (out)
                    out_offset += buffer_write(ctx,
                            out,
                            out_offset,
                            this_bytes * 8);
                if (in)
                    in_offset += buffer_add_read(ctx,
                            in,
                            in_offset,
                            this_bytes * 8,
                            0);
                if (!out && !in)
                    for (unsigned n = 0; n < this_bytes; n++)
                        buffer_write_byte(ctx, 0x00);
                length -= this_bytes * 8;
依照不同狀況,分別使用對應的MPSSE Command:
這邊的TMS Opertaion主要是用在JTAG TAP State Machine的控制上,讓JTAG進入到指定的State中!
一樣先上程式碼,請參考(src/jtag/drivers/mpsse.c):
void mpsse_clock_tms_cs(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in,
    unsigned in_offset, unsigned length, bool tdi, uint8_t mode)
{
    DEBUG_IO("%sout %d bits, tdi=%d", in ? "in" : "", length, tdi);
    assert(out);
    if (ctx->retval != ERROR_OK) {
        DEBUG_IO("Ignoring command due to previous error");
        return;
    }
    mode |= 0x42;
    if (in)
        mode |= 0x20;
    while (length > 0) {
        /* Guarantee buffer space enough for a minimum size transfer */
        if (buffer_write_space(ctx) < 3 || (in && buffer_read_space(ctx) < 1))
            ctx->retval = mpsse_flush(ctx);
        /* Byte transfer */
        unsigned this_bits = length;
        /* MPSSE command limit */
        /* NOTE: there's a report of an FT2232 bug in this area, where shifting
         * exactly 7 bits can make problems with TMS signaling for the last
         * clock cycle:
         *
         * http://developer.intra2net.com/mailarchive/html/libftdi/2009/msg00292.html
         */
        if (this_bits > 7)
            this_bits = 7;
        if (this_bits > 0) {
            buffer_write_byte(ctx, mode);
            buffer_write_byte(ctx, this_bits - 1);
            uint8_t data = 0;
            /* TODO: Fix MSB first, if allowed in MPSSE */
            bit_copy(&data, 0, out, out_offset, this_bits);
            out_offset += this_bits;
            buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
            if (in)
                in_offset += buffer_add_read(ctx,
                        in,
                        in_offset,
                        this_bits,
                        8 - this_bits);
            length -= this_bits;
        }
    }
}
首先將對應Write TMS的Bit拉起來,並判斷是否要同時讀取TDO
    mode |= 0x42;
    if (in)
        mode |= 0x20;
接著以7個Bits為一單位,依序將TMS送出:
        if (this_bits > 7)
            this_bits = 7;
        if (this_bits > 0) {
            buffer_write_byte(ctx, mode);
            buffer_write_byte(ctx, this_bits - 1);
            uint8_t data = 0;
            /* TODO: Fix MSB first, if allowed in MPSSE */
            bit_copy(&data, 0, out, out_offset, this_bits);
            out_offset += this_bits;
            buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00));
            if (in)
                in_offset += buffer_add_read(ctx,
                        in,
                        in_offset,
                        this_bits,
                        8 - this_bits);
            length -= this_bits;
        }
送出的方法也很簡單啦,主要是用以下Command:
別忘記這邊有個小小"tricky"的地方!
就是Byte1的Bit 7在TMS開始傳輸前,「會被放到TDI上」
bool tdi就是用來判斷TDI是否需要特別寫出"1"!
  
  
  
看完上面底層MPSSE的操作後,接下來我們來研究下JTAG Command是如何被執行的!
一般在上層的應用當中,會先將所需要讀/寫資料的動作轉成對應的JTAG Command後,
推入OpenOCD內部的JTAG Queue中先存放,比方說下面這個例子:
EX: 讀取IDCODE
static int scan_idcode(struct target *target)
{
        struct scan_field field;
        uint8_t in_value[4];
        jtag_add_ir_scan(target->tap, &select_idcode, TAP_IDLE);
        field.num_bits = 32;
        field.out_value = NULL;
        field.in_value = in_value;
        jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
        int retval = jtag_execute_queue();
        if (retval != ERROR_OK) {
                LOG_ERROR("failed jtag scan: %d", retval);
                return retval;
        }
        uint32_t in = buf_get_u32(field.in_value, 0, 32);
        LOG_DEBUG("IDCODE: 0x0 -> 0x%x", in);
        retrun ERROR_OK
}
從這個例子當中,可以看到讀取IDCODE這件事可以分成三個步驟:
接下來重點來啦,讓我們先看看JTAG中TAP的State Machine(狀態機):

---引用自OpenOCD Developer's Guide - OpenOCD JTAG Primer
這邊先說一下,不曉得為啥外部連結圖片的功能突然顯示不出來,只好直接上傳到這邊
讓我們將上面三個步驟拆成N個詳細的JTAG操作(假設狀態機一開始就在Run-Test/Idle中,IR指令的長度為5-bits):
不過這個步驟是有問題的!!!
請看Step 2地方! 這邊應該只需要敲入4-bits,剩下的一個bits,應該留到Step 3,
Path Move敲入TSM = 1的時候,同時把TDI = MSB的0x01 = 0x0敲入!
同理,Step 7的地方,這邊不需要讀32-bits,應該是讀31-bits才對!
整個詳細的步驟變成下圖:

  
  
  
以上概念講完了,回到程式碼中,讓我們看看jtag_execute_queue()中做了什麼事情,
請參考(src/jtag/core.c、src/jtag/drivers/driver.c)
int jtag_execute_queue(void)
{
    jtag_execute_queue_noclear();
    return jtag_error_clear();
}
void jtag_execute_queue_noclear(void)
{
    jtag_flush_queue_count++;
    jtag_set_error(interface_jtag_execute_queue());
....後面不重要
}
int interface_jtag_execute_queue(void)
{
    static int reentry;
    assert(reentry == 0);
    reentry++;
    int retval = default_interface_jtag_execute_queue();
    
    ....後面不重要
}
int default_interface_jtag_execute_queue(void)
{
    if (NULL == jtag) {
        LOG_ERROR("No JTAG interface configured yet.  "
            "Issue 'init' command in startup scripts "
            "before communicating with targets.");
        return ERROR_FAIL;
    }
    int result = jtag->execute_queue();
    ....後面不重要
}
走到這裡,終於看到jtag->execute_queue(),也就是呼叫ftdi_execute_queue()來處理這些JTAG Commands!
  
  
基本上Execute Queue很簡單,就是把JTAG Command Queue中的Command一個個讀出來,
然後判斷要執行的動作,再呼叫對應處理的函式去處理!
ftdi_execute_queue()內容如下,請參考(src/jtag/drivers/ftdi.c):
static int ftdi_execute_queue(void)
{
    /* blink, if the current layout has that feature */
    struct signal *led = find_signal_by_name("LED");
    if (led)
        ftdi_set_signal(led, '1');  ///譯註: 閃爍LED,基本上就是把那根訊號的Value從0變1或是1變0
    for (struct jtag_command *cmd = jtag_command_queue; cmd; cmd = cmd->next) {
        /* fill the write buffer with the desired command */
        ftdi_execute_command(cmd);  ///譯註: 一個個執行JTAG Command
    }
    if (led)
        ftdi_set_signal(led, '0');
    int retval = mpsse_flush(mpsse_ctx);
    if (retval != ERROR_OK)
        LOG_ERROR("error while flushing MPSSE queue: %d", retval);
    return retval;
}
然後在ftdi_execute_command()中就會呼叫對應的處理函式,
請參考(src/jtag/drivers/ftdi.c):
static void ftdi_execute_command(struct jtag_command *cmd)
{
    switch (cmd->type) {
        case JTAG_RESET:
            ftdi_execute_reset(cmd);
            break;
        case JTAG_RUNTEST:
            ftdi_execute_runtest(cmd);
            break;
        case JTAG_TLR_RESET:
            ftdi_execute_statemove(cmd);
            break;
        case JTAG_PATHMOVE:
            ftdi_execute_pathmove(cmd);
            break;
        case JTAG_SCAN:
            ftdi_execute_scan(cmd);     ///譯註: IR/DR Scan處理
            break;
        case JTAG_SLEEP:
            ftdi_execute_sleep(cmd);
            break;
        case JTAG_STABLECLOCKS:
            ftdi_execute_stableclocks(cmd);
            break;
        case JTAG_TMS:
            ftdi_execute_tms(cmd);
            break;
        default:
            LOG_ERROR("BUG: unknown JTAG command type encountered: %d", cmd->type);
            break;
    }
}
基本上接下來的章節只會提到ftdi_execute_scan(),其餘有興趣的讀者,
可以自行研究看看!其實是筆者沒時間去研究其他的操作 囧rz
  
  
先來個程式碼看看,請參考(src/jtag/drivers/ftdi.c):
static void ftdi_execute_scan(struct jtag_command *cmd)
{
    DEBUG_JTAG_IO("%s type:%d", cmd->cmd.scan->ir_scan ? "IRSCAN" : "DRSCAN",
        jtag_scan_type(cmd->cmd.scan));
    /* Make sure there are no trailing fields with num_bits == 0, or the logic below will fail. */
    while (cmd->cmd.scan->num_fields > 0
            && cmd->cmd.scan->fields[cmd->cmd.scan->num_fields - 1].num_bits == 0) {
        cmd->cmd.scan->num_fields--;
        LOG_DEBUG("discarding trailing empty field");
    }
    if (cmd->cmd.scan->num_fields == 0) {
        LOG_DEBUG("empty scan, doing nothing");
        return;
    }
    if (cmd->cmd.scan->ir_scan) {
        if (tap_get_state() != TAP_IRSHIFT)
            move_to_state(TAP_IRSHIFT);
    } else {
        if (tap_get_state() != TAP_DRSHIFT)
            move_to_state(TAP_DRSHIFT);
    }
    ftdi_end_state(cmd->cmd.scan->end_state);
    struct scan_field *field = cmd->cmd.scan->fields;
    unsigned scan_size = 0;
    for (int i = 0; i < cmd->cmd.scan->num_fields; i++, field++) {
        scan_size += field->num_bits;
        DEBUG_JTAG_IO("%s%s field %d/%d %d bits",
            field->in_value ? "in" : "",
            field->out_value ? "out" : "",
            i,
            cmd->cmd.scan->num_fields,
            field->num_bits);
        if (i == cmd->cmd.scan->num_fields - 1 && tap_get_state() != tap_get_end_state()) {
            /* Last field, and we're leaving IRSHIFT/DRSHIFT. Clock last bit during tap
             * movement. This last field can't have length zero, it was checked above. */
            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits - 1,
                ftdi_jtag_mode);
            uint8_t last_bit = 0;
            if (field->out_value)
                bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
            uint8_t tms_bits = 0x01;
            mpsse_clock_tms_cs(mpsse_ctx,
                    &tms_bits,
                    0,
                    field->in_value,
                    field->num_bits - 1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 1));
            mpsse_clock_tms_cs_out(mpsse_ctx,
                    &tms_bits,
                    1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 0));
        } else
            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits,
                ftdi_jtag_mode);
    }
    if (tap_get_state() != tap_get_end_state())
        move_to_state(tap_get_end_state());
    DEBUG_JTAG_IO("%s scan, %i bits, end in %s",
        (cmd->cmd.scan->ir_scan) ? "IR" : "DR", scan_size,
        tap_state_name(tap_get_end_state()));
}
落落長! 沒關係!
我們就用上面章節介紹到讀取IDCODE為例子,來分析一下程式碼的部分,
並對照Log,看看實際執行的結果!
讓我們開始吧!!
首先一開始會判斷目前的Scan種類,區分成IR-Scan和DR-Scan:
    if (cmd->cmd.scan->ir_scan) {
        if (tap_get_state() != TAP_IRSHIFT)
            move_to_state(TAP_IRSHIFT);
    } else {
        if (tap_get_state() != TAP_DRSHIFT)
            move_to_state(TAP_DRSHIFT);
    }
以IDCODE的例子來說,第一個Scan是IR-Scan,也就是執行move_to_state(TAP_IRSHIFT);,將目前的狀態機從"RUN/IDLE"移動到"IRSHIFT",簡單的來說就是對TDI依序寫入1(LSB) -> 1 -> 0 -> 0(MSB)!
總共長度為4-bits、資料轉成Byte為0x3(0011)!
讓我們來瞧瞧實際執行的情況!
Debug: 40752 821 ftdi.c:265 move_to_state(): start=RUN/IDLE goal=IRSHIFT
Debug: 40753 821 ftdi.c:269 move_to_state(): tap_set_state(DRSELECT)
Debug: 40754 821 ftdi.c:269 move_to_state(): tap_set_state(IRSELECT)
Debug: 40755 821 ftdi.c:269 move_to_state(): tap_set_state(IRCAPTURE)
Debug: 40756 821 ftdi.c:269 move_to_state(): tap_set_state(IRSHIFT)
Debug: 40757 821 mpsse.c:573 mpsse_clock_tms_cs(): out 4 bits, tdi=0
Debug: 40758 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40759 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40760 821 mpsse.c:455 buffer_write_byte(): 03
你看看! 你看看! 就是這麼簡單!!!!
既然到IRSHIFT,再來就是把IDCODE(0x1)給輸入TDI中:
            mpsse_clock_data(mpsse_ctx,
                field->out_value,
                0,
                field->in_value,
                0,
                field->num_bits - 1,
                ftdi_jtag_mode);
注意IR的長度雖然為5-bits,不過這邊只需要將前4-bit寫入就行了!
以下是實際執行的情況:
Debug: 40762 821 mpsse.c:497 mpsse_clock_data(): out 4 bits
Debug: 40763 821 mpsse.c:455 buffer_write_byte(): 1b
Debug: 40764 821 mpsse.c:455 buffer_write_byte(): 03
Debug: 40765 821 mpsse.c:463 buffer_write(): 4 bits
再來就是將IR最後的MSB敲出,並在TMS上輸入1,將狀態機從IRSHITF離開,進入到IREXIT1中:
            uint8_t last_bit = 0;
            if (field->out_value)
                bit_copy(&last_bit, 0, field->out_value, field->num_bits - 1, 1);
            uint8_t tms_bits = 0x01;
            mpsse_clock_tms_cs(mpsse_ctx,
                    &tms_bits,
                    0,
                    field->in_value,
                    field->num_bits - 1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 1));
來看實際成果:
Debug: 40766 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40767 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40768 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40769 821 mpsse.c:455 buffer_write_byte(): 01
Debug: 40770 821 ftdi.c:494 ftdi_execute_scan(): tap_set_state(IREXIT1)
完美!
接下來就是從IREXIT1,移動到IRPAUSE:
            mpsse_clock_tms_cs_out(mpsse_ctx,
                    &tms_bits,
                    1,
                    1,
                    last_bit,
                    ftdi_jtag_mode);
            tap_set_state(tap_state_transition(tap_get_state(), 0));
Log:
Debug: 40771 821 mpsse.c:573 mpsse_clock_tms_cs(): out 1 bits, tdi=0
Debug: 40772 821 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40773 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40774 821 mpsse.c:455 buffer_write_byte(): 00
Debug: 40775 822 ftdi.c:501 ftdi_execute_scan(): tap_set_state(IRPAUSE)
最後,當然要從目前的"IRPAUSE"回到"RUN/IDLE"的狀態啦!
    if (tap_get_state() != tap_get_end_state())
        move_to_state(tap_get_end_state());
Log:
Debug: 40776 822 ftdi.c:265 move_to_state(): start=IRPAUSE goal=RUN/IDLE
Debug: 40777 822 ftdi.c:269 move_to_state(): tap_set_state(IREXIT2)
Debug: 40778 822 ftdi.c:269 move_to_state(): tap_set_state(IRUPDATE)
Debug: 40779 822 ftdi.c:269 move_to_state(): tap_set_state(RUN/IDLE)
Debug: 40780 822 mpsse.c:573 mpsse_clock_tms_cs(): out 3 bits, tdi=0
Debug: 40781 822 mpsse.c:455 buffer_write_byte(): 4b
Debug: 40782 822 mpsse.c:455 buffer_write_byte(): 02
Debug: 40783 822 mpsse.c:455 buffer_write_byte(): 03
以上就是簡單的IR Scan分析!
DR Scan也是類似的方式,就不多做說明! 篇幅太長,打字很累!
  
  
  
痾! 花了這麼長的篇幅,終於將JTAG Command轉MPSSE Command的部分剖析完畢!完整的 剖析了整個FT2232H底層的運作和實際OpenOCD5中MPSSE程式碼的部分!
如果對上述內容還是有不清楚的地方,建議把JTAG TAP State Machine印出來,
然後用紙筆來模擬訊號的進出,相信對整個流程會有更深入的了解!!!
真是一場鏖戰,打完這篇大概花了我半條命......!